package net.fornwall.eclipsecoder.csharpsupport;

import java.lang.reflect.Array;

import net.fornwall.eclipsecoder.stats.CodeGenerator;
import net.fornwall.eclipsecoder.stats.ProblemStatement;

public class CSharpCodeGenerator extends CodeGenerator {

	public CSharpCodeGenerator(ProblemStatement problemStatement) {
		super(problemStatement);
	}

	private String getCreateStatement(Object value, String variableName) {
		Class<?> type = value.getClass();
		if (type.isArray()) {
			StringBuilder builder = new StringBuilder(getTypeString(type) + " " + variableName + " = new "
					+ getTypeString(type) + "{");
			for (int i = 0; i < Array.getLength(value); i++) {
				if (i != 0) {
					builder.append(", ");
				}
				builder.append(getSimpleCreateStatement(Array.get(value, i)));
			}
			builder.append("};\n");
			return builder.toString();
		}

		return getTypeString(type) + " " + variableName + " = " + getSimpleCreateStatement(value) + ";\n";
	}

	@Override
	public String getDummyReturnString() {
		if (problemStatement.getReturnType() == Integer.class || problemStatement.getReturnType() == Long.class
				|| problemStatement.getReturnType() == Character.class) {
			return "0";
		} else if (problemStatement.getReturnType() == Double.class) {
			return "0.0";
		} else {
			return "null";
		}
	}

	private String getSimpleCreateStatement(Object value) {
		if (value.getClass() == Character.class) {
			char c = (Character) value;
			return "'" + (c == '\'' ? "\\'" : value) + "'";
		} else if (value.getClass() == String.class) {
			return '"' + value.toString() + '"';
		} else {
			return value.toString() + ((value instanceof Long) ? "L" : "");
		}
	}

	@Override
	public String getTestsSource() {
		String testClassName = problemStatement.getClassName() + "Test";
		StringBuilder out = new StringBuilder("using System;\n");
		if (problemStatement.getReturnType().isArray()) {
			out.append("using System.Collections;\n");
		}
		out.append("\npublic class " + testClassName + " {\n\n");

		out.append("\tprivate " + problemStatement.getSolutionClassName() + " solution = new "
				+ problemStatement.getSolutionClassName() + "();\n");

		if (problemStatement.getReturnType().isArray()) {
			out.append("\n\tprivate static string ToString(");
			out.append(getTypeString(problemStatement.getReturnType()));
			out.append("a) {\n");
			out.append("\t\tstring result = \"{\";\n");
			out.append("\t\tforeach (");
			out.append(getTypeString(problemStatement.getReturnType().getComponentType()));
			out.append(" e in a) result += ((result.Length == 1) ? \"\" : \", \") + e;\n");
			out.append("\t\treturn result + \"}\";\n");
			out.append("\t}\n");
		}
		
		out.append("\n\tpublic static void assertEquals(int testCase, ");
		out.append(getTypeString(problemStatement.getReturnType()));
		out.append(" expected, ");
		out.append(getTypeString(problemStatement.getReturnType()));
		out.append(" actual) {\n");
		if (problemStatement.getReturnType().isArray()) {
			out.append("\t\tbool same = true;\n");
			out.append("\t\tif (actual == null || (expected.Length != actual.Length)) {\n");
			out.append("\t\t\tsame = false;\n");
			out.append("\t\t} else {\n");
			out.append("\t\t\tfor (int i = 0; i < expected.Length; i++)\n");
			out.append("\t\t\tif (expected[i] != actual[i]) same = false;\n");
			out.append("\t\t}\n");
			out.append("\t\tif (same) {\n");
		} else if (problemStatement.getReturnType() == Double.class) {
			out.append("\t\tdouble delta = Math.Max(1e-9, 1e-9 * Math.Abs(expected));\n");
			out.append("\t\tif (Math.Abs(expected - actual) <= delta) {\n");
		} else {
			out.append("\t\tif (expected == actual) {\n");
		}
		out.append("\t\t\tConsole.WriteLine(\"Test case \" + testCase + \" PASSED!\");\n");
		out.append("\t\t} else {\n");
		out
				.append("\t\t\tConsole.WriteLine(\"Test case \" + testCase + \" FAILED! Expected \" + ");
		if (problemStatement.getReturnType().isArray())
			out.append("ToString(");
		out.append("expected");
		if (problemStatement.getReturnType().isArray())
			out.append(")");
		out.append("+ \", got \" + ");
		if (problemStatement.getReturnType().isArray() || (problemStatement.getReturnType() == String.class))
			out.append("((actual == null) ? \"null\" : ");
		if (problemStatement.getReturnType().isArray())
			out.append("ToString(");
		out.append("actual");
		if (problemStatement.getReturnType().isArray())
			out.append(")");
		if (problemStatement.getReturnType().isArray() || (problemStatement.getReturnType() == String.class))
			out.append(")");
		out.append(");\n");
		out.append("\t\t}\n");
		out.append("\t}\n");

		for (int i = 0; i < problemStatement.getTestCases().size(); i++) {
			ProblemStatement.TestCase testCase = problemStatement.getTestCases().get(i);
			out.append("\n\tvoid testCase" + i + "() {\n");

			for (int paramNumber = 0; paramNumber < testCase.getParameters().length; paramNumber++) {
				out.append("\t\t");
				out.append(getCreateStatement(testCase.getParameters()[paramNumber], problemStatement
						.getParameterNames().get(paramNumber)));
			}

			out.append("\t\t");
			out.append(getCreateStatement(testCase.getReturnValue(), "expected_"));

			out.append("\t\tassertEquals(" + i + ", expected_, solution." + problemStatement.getSolutionMethodName()
					+ "(");
			for (int param = 0; param < problemStatement.getParameterNames().size(); param++) {
				if (param != 0)
					out.append(", ");
				out.append(problemStatement.getParameterNames().get(param));
			}

			out.append("));\n\t}\n");
		}

		out.append("\n\tpublic static void Main() {\n");
		for (int i = 0; i < problemStatement.getTestCases().size(); i++) {
			out.append("\t\ttry { new " + testClassName + "().testCase" + i + "(); } catch (Exception e) {");
			out.append(" Console.WriteLine(\"Test case " + i + " FAILED! Exception: \" + e); }\n");
		}
		out.append("\t}\n\n}\n");

		return out.toString();
	}

	@Override
	public String getTypeString(Class<?> type) {
		if (type == Integer.class) {
			return "int";
		} else if (type == Character.class) {
			return "char";
		} else if (type == Double.class) {
			return "double";
		} else if (type == Long.class) {
			return "long";
		} else if (type.isArray()) {
			return getTypeString(type.getComponentType()) + "[]";
		} else {
			return "string";
		}
	}

}
